/*
// Licensed to Julian Hyde under one or more contributor license
// agreements. See the NOTICE file distributed with this work for
// additional information regarding copyright ownership.
//
// Julian Hyde licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
 */
package org.olap4j.mdx;

import java.io.PrintWriter;
import java.util.List;

import static org.olap4j.mdx.MdxUtil.unparseList;

/**
 * Enumerated values describing the syntax of an expression.
 * 
 * @author jhyde
 * @since 21 July, 2003
 */
public enum Syntax {
	/**
	 * Defines syntax for expression invoked <code>FUNCTION()</code> or <code>FUNCTION(args)</code>.
	 */
	Function {
		public void unparse(String operatorName, List<ParseTreeNode> argList, ParseTreeWriter writer) {
			unparseList(writer, argList, operatorName + "(", ", ", ")");
		}
	},

	/**
	 * Defines syntax for expression invoked as <code>object.PROPERTY</code>.
	 */
	Property {
		public void unparse(String operatorName, List<ParseTreeNode> argList, ParseTreeWriter writer) {
			assert argList.size() == 1;
			argList.get(0).unparse(writer); // 'this'
			writer.getPrintWriter().print(".");
			writer.getPrintWriter().print(operatorName);
		}
	},

	/**
	 * Defines syntax for expression invoked invoked as <code>object.METHOD()</code> or <code>object.METHOD(args)</code>
	 * .
	 */
	Method {
		public void unparse(String operatorName, List<ParseTreeNode> argList, ParseTreeWriter writer) {
			assert argList.size() >= 1;
			argList.get(0).unparse(writer); // 'this'
			final PrintWriter pw = writer.getPrintWriter();
			pw.print(".");
			pw.print(operatorName);
			pw.print("(");
			for (int i = 1; i < argList.size(); i++) {
				if (i > 1) {
					pw.print(", ");
				}
				argList.get(i).unparse(writer);
			}
			pw.print(")");
		}
	},

	/**
	 * Defines syntax for expression invoked as <code>arg OPERATOR arg</code> (like '+' or 'AND').
	 */
	Infix {
		public void unparse(String operatorName, List<ParseTreeNode> argList, ParseTreeWriter writer) {
			if (needParen(argList)) {
				unparseList(writer, argList, "(", " " + operatorName + " ", ")");
			} else {
				unparseList(writer, argList, "", " " + operatorName + " ", "");
			}
		}
	},

	/**
	 * Defines syntax for expression invoked as <code>OPERATOR arg</code> (like unary '-').
	 */
	Prefix {
		public void unparse(String operatorName, List<ParseTreeNode> argList, ParseTreeWriter writer) {
			if (needParen(argList)) {
				unparseList(writer, argList, "(" + operatorName + " ", null, ")");
			} else {
				unparseList(writer, argList, operatorName + " ", null, "");
			}
		}
	},

	/**
	 * Defines syntax for expression invoked as <code>arg OPERATOR</code> (like <code>IS EMPTY</code>).
	 */
	Postfix {
		public void unparse(String operatorName, List<ParseTreeNode> argList, ParseTreeWriter writer) {
			if (needParen(argList)) {
				unparseList(writer, argList, "(", null, " " + operatorName + ")");
			} else {
				unparseList(writer, argList, "", null, " " + operatorName);
			}
		}
	},

	/**
	 * Defines syntax for expression invoked as <code>{ARG, &#46;&#46;&#46;}</code>; that is, the set construction
	 * operator.
	 */
	Braces {
		public void unparse(String operatorName, List<ParseTreeNode> argList, ParseTreeWriter writer) {
			unparseList(writer, argList, "{", ", ", "}");
		}
	},

	/**
	 * Defines syntax for expression invoked as <code>(ARG)</code> or <code>(ARG, &#46;&#46;&#46;)</code>; that is,
	 * parentheses for grouping expressions, and the tuple construction operator.
	 */
	Parentheses {
		public void unparse(String operatorName, List<ParseTreeNode> argList, ParseTreeWriter writer) {
			if (argList.size() == 1 && argList.get(0) instanceof CallNode
					&& needParen(((CallNode) argList.get(0)).getArgList())) {
				// The parenthesized expression is going to defensively
				// parenthesize itself. So, don't add another layer.
				argList.get(0).unparse(writer);
			} else {
				unparseList(writer, argList, "(", ", ", ")");
			}
		}
	},

	/**
	 * Defines syntax for expression invoked as <code>CASE ... END</code>.
	 */
	Case {
		public void unparse(String operatorName, List<ParseTreeNode> argList, ParseTreeWriter writer) {
			final PrintWriter pw = writer.getPrintWriter();
			if (operatorName.equals("_CaseTest")) {
				pw.print("CASE");
				int j = 0;
				int clauseCount = (argList.size() - j) / 2;
				for (int i = 0; i < clauseCount; i++) {
					pw.print(" WHEN ");
					argList.get(j++).unparse(writer);
					pw.print(" THEN ");
					argList.get(j++).unparse(writer);
				}
				if (j < argList.size()) {
					pw.print(" ELSE ");
					argList.get(j++).unparse(writer);
				}
				assert j == argList.size();
				pw.print(" END");
			} else {
				assert operatorName.equals("_CaseMatch");

				pw.print("CASE ");
				int j = 0;
				argList.get(j++).unparse(writer);
				int clauseCount = (argList.size() - j) / 2;
				for (int i = 0; i < clauseCount; i++) {
					pw.print(" WHEN ");
					argList.get(j++).unparse(writer);
					pw.print(" THEN ");
					argList.get(j++).unparse(writer);
				}
				if (j < argList.size()) {
					pw.print(" ELSE ");
					argList.get(j++).unparse(writer);
				}
				assert j == argList.size();
				pw.print(" END");
			}
		}
	},

	/**
	 * Defines syntax for expression generated by the system which cannot be specified syntactically.
	 */
	Internal,

	/**
	 * Defines syntax for a CAST expression <code>CAST(expression AS type)</code>.
	 */
	Cast {
		public void unparse(String operatorName, List<ParseTreeNode> argList, ParseTreeWriter writer) {
			writer.getPrintWriter().print("CAST(");
			argList.get(0).unparse(writer);
			writer.getPrintWriter().print(" AS ");
			argList.get(1).unparse(writer);
			writer.getPrintWriter().print(")");
		}
	},

	/**
	 * Defines syntax for expression invoked <code>object&#46;&PROPERTY</code> (a variant of {@link #Property}).
	 */
	QuotedProperty {
		public void unparse(String operatorName, List<ParseTreeNode> argList, ParseTreeWriter writer) {
			assert argList.size() == 1;
			argList.get(0).unparse(writer); // 'this'
			final PrintWriter pw = writer.getPrintWriter();
			pw.print(".[");
			pw.print(operatorName);
			pw.print("]");
		}
	},

	/**
	 * Defines syntax for expression invoked <code>object&#46;[&PROPERTY]</code> (a variant of {@link #Property}).
	 */
	AmpersandQuotedProperty {
		public void unparse(String operatorName, List<ParseTreeNode> argList, ParseTreeWriter writer) {
			assert argList.size() == 1;
			argList.get(0).unparse(writer); // 'this'
			final PrintWriter pw = writer.getPrintWriter();
			pw.print(".&[");
			pw.print(operatorName);
			pw.print("]");
		}
	},

	/**
	 * Defines the syntax for an empty expression. Empty expressions can occur within function calls, and are denoted by
	 * a pair of commas with only whitespace between them, for example
	 * 
	 * <blockquote> <code>DrillDownLevelTop({[Product].[All Products]}, 3, ,
	 *  [Measures].[Unit Sales])</code> </blockquote>
	 */
	Empty {
		public void unparse(String operatorName, List<ParseTreeNode> argList, ParseTreeWriter writer) {
			assert argList.size() == 0;
		}
	};

	/**
	 * Converts a call to a function of this syntax into source code.
	 * 
	 * @param operatorName
	 *            Operator name
	 * @param argList
	 *            List of arguments
	 * @param writer
	 *            Writer
	 */
	public void unparse(String operatorName, List<ParseTreeNode> argList, ParseTreeWriter writer) {
		throw new UnsupportedOperationException();
	}

	/**
	 * Returns whether a collection of parse tree nodes need to be enclosed in parentheses.
	 * 
	 * @param args
	 *            Parse tree nodes
	 * @return Whether nodes need to be enclosed in parentheses
	 */
	private static boolean needParen(List<ParseTreeNode> args) {
		return !(args.size() == 1 && args.get(0) instanceof CallNode && ((CallNode) args.get(0)).getSyntax() == Parentheses);
	}
}

// End Syntax.java
